`include "cpu_def.vh"

module write_back(
  input clk,
  input rst,
  
  (*mark_debug = "true"*)input        valid_i       ,          
  (*mark_debug = "true"*)input [31:0] pc_i          ,             
  input [31:0] alu_result_i  ,     
  input [63:0] div_rs_i      ,
  input        wr_mem_i      ,   
  input        write_rf_i    ,    
  input [ 4:0] rf_waddr_i    ,    
  input [ 2:0] sel_rf_wdata_i,
  input [ 1:0] write_hl_i    ,    
  input        sel_hl_wdata_i,

  input [ 2:0] cp0_wsel_i    ,
  input [ 4:0] cp0_wreg_num_i,
  input        cp0_wen_i     ,
  input [31:0] cp0_wdata_i   ,
  input [31:0] cp0_rdata_i   ,
  input        ft_tlbre_i    ,
  input        ft_tlbi_i     ,
  input        sys_i         ,
  input        brk_i         ,
  input        ov_i          ,
  input        adel_i        ,
  input        ades_i        ,
  input        tlbrel_i      ,
  input        tlbres_i      ,
  input        tlbil_i       ,
  input        tlbis_i       ,
  input        tlbm_i        ,
  input        ft_adel_i     ,
  input        ri_i          ,
  input        int_i         ,
  input [31:0] badvaddr_i    ,
  input        eret_i        ,
  input        bd_i          ,
  input        tlbp_i        ,
  input        tlbr_i        ,
  input        tlbwi_i       ,
  input        tlbwr_i       ,
  input        lsu_stall_wb_i,

  input [63:0] mul_result_i  ,
  input        lsu_valid_i   ,
  input [31:0] lsu_ld_data_i ,

  output [31:0] pc_o      ,
  output        rf_wen_o  ,
  output [ 4:0] rf_wnum_o ,
  output [31:0] rf_wdata_o,
  output [ 1:0] hl_wen_o  ,
  output [63:0] hl_wdata_o,

  // to cp0

  output        bd_o          ,
  output        tlbre_taken_o ,
  output        ex_taken_o    ,
  output        eret_taken_o  ,
  output [ 4:0] ex_code_o     ,
  output        cp0_wen_o     ,
  output [ 4:0] cp0_wreg_num_o,
  output [ 2:0] cp0_wsel_o    ,
  output [31:0] cp0_wdata_o   ,
  output [31:0] addr_o        ,
  output        tlbp_taken_o  ,
  output        tlbr_taken_o  ,
  output        tlbwi_taken_o ,
  output        tlbwr_taken_o ,

  // to hazard
  output        stall_req_o,
  output        flush_req_o,
  output [31:0] tlb_pc_o,

  // to forward path
  output        write_rf_o,
  output [31:0] write_rf_data_o,
  output [ 1:0] write_hl_o,

  // to de
  output ld_o,
  output mul_o
);

  assign stall_req_o = (
    // lsu_stall_wb_i
    wr_mem_i && !lsu_valid_i
  ) && valid_i && !ex_taken_o;
  assign flush_req_o = 
    ex_taken_o    || 
    eret_taken_o  ||
    tlbr_taken_o  ||
    tlbwi_taken_o ||
    tlbwr_taken_o ||
    tlbp_taken_o  ;

  wire commit = valid_i && !stall_req_o;
  reg [31:0] cycles;
  reg [31:0] instrs;
  reg [31:0] ld_instrs;
  reg [31:0] st_instrs;

  always@(posedge clk) begin
    if (rst) begin
      cycles <= 0;
    end else begin
      cycles <= cycles + 1;
    end
  end

  always@(posedge clk) begin
    if (rst) begin
      instrs <= 0;
    end else if (commit) begin
      instrs <= instrs + 1;
    end
  end

  always@(posedge clk) begin
    if (rst) begin
      ld_instrs <= 0;
    end else if (commit && ld_o) begin
      ld_instrs <= ld_instrs + 1;
    end
  end

  always@(posedge clk) begin
    if (rst) begin
      st_instrs <= 0;
    end else if (commit && !ld_o && wr_mem_i) begin
      st_instrs <= st_instrs + 1;
    end
  end

  assign pc_o       = pc_i;
  assign rf_wen_o   = write_rf_i && valid_i && !ex_taken_o && !stall_req_o;
  assign rf_wnum_o  = rf_waddr_i;
  assign rf_wdata_o = 
    {32{sel_rf_wdata_i == 3'b000}} & alu_result_i      |
    {32{sel_rf_wdata_i == 3'b001}} & lsu_ld_data_i     |
    {32{sel_rf_wdata_i == 3'b010}} & (pc_i + 8)        |
    {32{sel_rf_wdata_i == 3'b011}} & cp0_rdata_i       |
    {32{sel_rf_wdata_i == 3'b100}} & mul_result_i[31:0];

  assign hl_wen_o = write_hl_i & {2{valid_i}} & {2{!ex_taken_o}} & {2{!stall_req_o}};
  assign hl_wdata_o = sel_hl_wdata_i ? div_rs_i : mul_result_i;

  assign bd_o           = bd_i;
  assign tlbre_taken_o  = 
    ft_tlbre_i ||
    tlbrel_i   ||
    tlbres_i   ;
  assign ex_taken_o     = (
    ft_tlbre_i ||
    ft_tlbi_i  ||
    sys_i      ||
    brk_i      ||
    ov_i       ||
    adel_i     ||
    ades_i     ||
    tlbrel_i   ||
    tlbres_i   ||
    tlbil_i    ||
    tlbis_i    ||
    tlbm_i     ||
    ft_adel_i  ||
    ri_i       || 
    int_i        
  ) && valid_i;
  assign eret_taken_o   = eret_i && valid_i && !ex_taken_o && !stall_req_o;
  assign ex_code_o      =  
    int_i                     ? `EX_INT :
    ft_adel_i                 ? `EX_ADEL:
    (ft_tlbre_i || ft_tlbi_i) ? `EX_TLBL:
    ri_i                      ? `EX_RI  :
    (sys_i || brk_i || ov_i)  ? (
      {5{sys_i}} & `EX_SYS |
      {5{brk_i}} & `EX_BP  |
      {5{ov_i }} & `EX_OV  
    ) :
    (adel_i || ades_i      )  ? (
      {5{adel_i}} & `EX_ADEL |
      {5{ades_i}} & `EX_ADES
    ) :
    (tlbrel_i || tlbres_i || tlbil_i || tlbis_i || tlbm_i) ? (
      {5{tlbrel_i || tlbil_i}} & `EX_TLBL |
      {5{tlbres_i || tlbis_i}} & `EX_TLBS |
      {5{tlbm_i             }} & `EX_TLBM
    ) :
    5'd0;
    
  assign cp0_wen_o      = cp0_wen_i && valid_i && !ex_taken_o && !stall_req_o;
  assign cp0_wreg_num_o = cp0_wreg_num_i;
  assign cp0_wsel_o     = cp0_wsel_i;
  assign cp0_wdata_o    = cp0_wdata_i;
  assign addr_o = (
    adel_i   || 
    ades_i   || 
    tlbrel_i || 
    tlbres_i || 
    tlbil_i  || 
    tlbis_i  || 
    tlbm_i
  ) ? badvaddr_i : pc_i;

  assign tlbp_taken_o  = tlbp_i  && valid_i && !ex_taken_o && !stall_req_o;
  assign tlbr_taken_o  = tlbr_i  && valid_i && !ex_taken_o && !stall_req_o;
  assign tlbwi_taken_o = tlbwi_i && valid_i && !ex_taken_o && !stall_req_o;
  assign tlbwr_taken_o = tlbwr_i && valid_i && !ex_taken_o && !stall_req_o;

  assign tlb_pc_o = pc_i + 4;

  assign write_rf_o = write_rf_i && valid_i;
  assign write_rf_data_o = sel_rf_wdata_i[1] ? 
                     (sel_rf_wdata_i[0] ? cp0_rdata_i : pc_i + 8    ) :
                     (sel_rf_wdata_i[0] ? 32'd0       : alu_result_i) ;
  // assign write_rf_data_o = sel_rf_wdata_i[1] ? 
  //                    (sel_rf_wdata_i[0] ? cp0_rdata_i   : pc_i + 8    ) :
  //                    (sel_rf_wdata_i[0] ? lsu_ld_data_i : alu_result_i) ;

  assign write_hl_o = write_hl_i & {2{valid_i}};

  assign ld_o = sel_rf_wdata_i == 3'b001;
  assign mul_o = sel_hl_wdata_i;
  
endmodule
